﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using EnvDTE;
using EnvDTE80;
using Microsoft.Cct.Automation;
using Microsoft.VisualStudio.Project.Automation;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Web.Application;

namespace AzurePlugin.Converter
{
    public class SolutionConverter
    {
        private readonly Solution2 _solution;
        private readonly IVsSolution _solutionService;
        private readonly ServiceProviderAdapter _serviceProvider;

        private readonly IList<ProjectAdapter> _nonConvertedWorkerProjects;
        private readonly IList<ProjectAdapter> _webProjects;
        private OAProject _azureProject;

        public SolutionConverter(Solution2 solution, ServiceProviderAdapter serviceProvider)
        {
            _solution = solution;
            _serviceProvider = serviceProvider;
            _solutionService = _serviceProvider.GetService<IVsSolution>();

            _azureProject = getExistingAzureProject();

            ConverterLogger.Instance.Info("conversion of " + Path.GetFileNameWithoutExtension(_solution.FullName) + " started");

            _nonConvertedWorkerProjects = getNonConvertedWorkerProjects(getSuitableWorkerProjects());
            if (_nonConvertedWorkerProjects.Count > 0)
            {
                ConverterLogger.Instance.Info(
                    "found {0} project(s) available for conversion to worker roles: {1}",
                    _nonConvertedWorkerProjects.Count,
                    string.Join(", ", _nonConvertedWorkerProjects.Select(x => x.Project.Name)));
            }

            _webProjects = getSuitableWebProjects();
            if (_webProjects.Count > 0)
            {
                ConverterLogger.Instance.Info(
                    "found {0} project(s) to be added as web roles: {1}",
                    _webProjects.Count,
                    string.Join(", ", _webProjects.Select(x => x.Project.Name)));
            }
        }

        [Obsolete]
        public void ConvertAll()
        {
            convert(_nonConvertedWorkerProjects, _webProjects);
        }

        public void Convert(OAProject azureProject, IEnumerable<ProjectAdapter> projects)
        {
            _azureProject = azureProject;
            convert(projects);
        }

        public void CreateAzureProjectAndConvert(string azureProjectName, IEnumerable<ProjectAdapter> projects)
        {
            createAzureProject(azureProjectName);
            convert(projects);
        }

        public IList<ProjectAdapter> GetConvertibleWebRoleProjects()
        {
            return _webProjects;
        }

        public IList<ProjectAdapter> GetConvertibleWorkerRoleProjects()
        {
            return _nonConvertedWorkerProjects;
        }

        public IList<ProjectAdapter> GetAzureProjects()
        {
            return _solution.Projects.OfType<CctOAProject>()
                .Select(x => new ProjectAdapter(x, _solutionService))
                .ToList();
        }

        private void convert(IEnumerable<ProjectAdapter> projects)
        {
            var workerProjects = new List<ProjectAdapter>();
            var webProjects = new List<ProjectAdapter>();

            foreach (var project in projects)
            {
                if (!_nonConvertedWorkerProjects.Contains(project) && !_webProjects.Contains(project))
                {
                    throw new ArgumentException("project {0} cannot be converted", project.Project.Name);
                }
                else if (_nonConvertedWorkerProjects.Contains(project))
                {
                    workerProjects.Add(project);
                }
                else
                {
                    webProjects.Add(project);
                }
            }

            convert(workerProjects, webProjects);
        }

        private void convert(IEnumerable<ProjectAdapter> workerProjects, IEnumerable<ProjectAdapter> webProjects)
        {
            try
            {
                _azureProject = getOrCreateAzureProject();

                ConverterLogger.Instance.Info("converting potential worker role projects");
                convertWorkerProjects(workerProjects);
                ConverterLogger.Instance.Info("conversion of potential worker role projects completed");

                ConverterLogger.Instance.Info("adding references for worker roles");
                addProjectReferences(_azureProject, workerProjects);
                ConverterLogger.Instance.Info("adding of references for worker roles completed");

                ConverterLogger.Instance.Info("adding references for web roles");
                addProjectReferences(_azureProject, webProjects);
                ConverterLogger.Instance.Info("adding of references for web roles completed");

                ConverterLogger.Instance.Info("activating Debug|Any CPU build configuration");
                // todo: seems that it's not necessary, but just in case
                foreach (SolutionConfiguration2 solutionConfiguration in _solution.SolutionBuild.SolutionConfigurations)
                {
                    if (solutionConfiguration.Name == "Debug" && solutionConfiguration.PlatformName == "Any CPU")
                    {
                        solutionConfiguration.Activate();
                    }
                }

                ConverterLogger.Instance.Info("changing projects' build configurations");
                changeProjectsBuildConfigurations(workerProjects);

                ConverterLogger.Instance.Info("conversion of " + Path.GetFileNameWithoutExtension(_solution.FullName) + " completed");
            }
            catch (Exception ex)
            {
                ConverterLogger.Instance.Error(ex);
            }
        }

        private OAProject getOrCreateAzureProject()
        {
            var result = getExistingAzureProject();

            if (result == null)
            {
                ConverterLogger.Instance.Info("existing azure project not found, adding a new one");
                createAzureProject();
                result = getExistingAzureProject();

                if (result != null)
                    ConverterLogger.Instance.Info("azure project created successfully");
            }

            return result;
        }

        private void changeProjectsBuildConfigurations(IEnumerable<ProjectAdapter> suitableWorkerProjects)
        {
            var projectNames = suitableWorkerProjects.Select(x => x.Project.UniqueName).ToList();

            var solutionFilePath = _solution.FullName;
            _solution.Close(true);
            var solutionFileContent = File.ReadAllText(solutionFilePath);

            foreach (var projectAdapter in suitableWorkerProjects)
            {
                solutionFileContent = Regex.Replace(solutionFileContent,
                                                    @"\{" + projectAdapter.Guid + @"\}\.Debug\|Any CPU\.ActiveCfg = (.*?)\|.*",
                                                    "{" + projectAdapter.Guid.ToString().ToUpper() + @"}.Debug|Any CPU.ActiveCfg = $1|Any CPU",
                                                    RegexOptions.IgnoreCase);
            }

            File.WriteAllText(solutionFilePath, solutionFileContent);

            _solution.Open(solutionFilePath);

            foreach (SolutionContext solutionContext in _solution.SolutionBuild.ActiveConfiguration.SolutionContexts)
            {
                if (projectNames.Contains(solutionContext.ProjectName))
                    solutionContext.ShouldBuild = true;
            }
        }

        private void convertWorkerProjects(IEnumerable<ProjectAdapter> projects)
	    {
            foreach (var projectAdapter in projects)
	        {
                new WorkerRoleProjectConverter(projectAdapter).Convert();
	        }
        }

        private IList<ProjectAdapter> getNonConvertedWorkerProjects(IEnumerable<ProjectAdapter> suitableWorkerProjects)
        {
            var result = new List<ProjectAdapter>();

            foreach (var projectAdapter in suitableWorkerProjects)
            {
                var roleType = projectAdapter.GetProperty(Constants.RoleTypePropertyName);
                if (roleType != Constants.WorkerRoleTypePropertyValue)
                {
                    result.Add(projectAdapter);
                }
            }

            return result;
        }

        private IList<ProjectAdapter> getSuitableWebProjects()
        {
            var allWebProjects = getWebProjects();
            var referencedWebProjects = getReferencedProjects();

            return allWebProjects
                .Where(x => !referencedWebProjects.Any(y => y.Guid == x.Guid))
                .ToList();
        }

        private IList<ProjectAdapter> getSuitableWorkerProjects()
        {
            var allWebProjects = getWebProjects();
            var allWorkerProjects = getWorkerProjects();
            var referencedProjects = getReferencedProjects();

            return allWorkerProjects
                .Where(x => !allWebProjects.Any(y => y.Guid == x.Guid))
                .Where(x => !referencedProjects.Any(y => y.Guid == x.Guid))
                .ToList();
        }

        private IList<ProjectAdapter> getWebProjects()
        {
            var result = new List<ProjectAdapter>();

            foreach (Project project in _solution.Projects)
            {
                var projectAdapter = new ProjectAdapter(project, _solutionService);
                if (projectAdapter.Hierarchy is IVsWebApplicationProject)
                {
                    result.Add(projectAdapter);
                }
            }

            return result;
        }

        private IList<ProjectAdapter> getWorkerProjects()
        {
            var result = new List<ProjectAdapter>();

            foreach (Project project in _solution.Projects)
            {
                var kindGuid = Guid.Parse(project.Kind);
                if (kindGuid == Constants.CSharpProjectTypeGuid || kindGuid == Constants.VBProjectTypeGuid)
                {
                    var projectAdapter = new ProjectAdapter(project, _solutionService);

                    // windows services has prjOutputTypeWinExe as an OutputType
                    if (projectAdapter.GetProperty("OutputType") == Constants.WinExeProjectOutputType)
                        result.Add(projectAdapter);
                }
            }

            return result;
        }

        private void addProjectReferences(OAProject azureProject, IEnumerable<ProjectAdapter> projects)
        {
            var referenceContainer = azureProject.Project.GetReferenceContainer();
            foreach (var projectAdapter in projects)
            {
                ConverterLogger.Instance.Info("adding reference into Azure project for " + projectAdapter.Project.Name);

                string projRef;
                _solutionService.GetProjrefOfProject(projectAdapter.Hierarchy, out projRef);

                referenceContainer.AddReferenceFromSelectorData(
                    new VSCOMPONENTSELECTORDATA
                    {
                        bstrFile = Path.GetDirectoryName(projectAdapter.Project.FullName),
                        bstrProjRef = projRef,
                        bstrTitle = projectAdapter.Project.Name,
                        type = VSCOMPONENTTYPE.VSCOMPONENTTYPE_Project
                    });
            }
        }

        private void createAzureProject()
        {
            const string ending = ".Azure";
            var name = Path.GetFileNameWithoutExtension(_solution.FullName) + ending;
            createAzureProject(name);
        }

        private void createAzureProject(string name)
        {
            var solutionPath = Path.GetDirectoryName(_solution.FullName);
            var projectPath = Path.Combine(solutionPath, name);
            var csTemplatePath = _solution.GetProjectTemplate("CloudService_cs.zip", "CSharp");
            _solution.AddFromTemplate(csTemplatePath, projectPath, name);
        }

        private CctOAProject getExistingAzureProject()
        {
            return _solution.Projects.OfType<CctOAProject>().FirstOrDefault();
        }

        private IList<ProjectAdapter> getReferencedProjects()
        {
            var result = new List<ProjectAdapter>();

            if (_azureProject == null)
                return result;

            var item = _azureProject.ProjectItems.OfType<OAReferenceFolderItem>().First();
            foreach (OAReferenceItem projectItem in item.ProjectItems)
            {
                var project = (projectItem.Object as OAProjectReference).SourceProject;
                result.Add(new ProjectAdapter(project, _solutionService));
            }

            return result;
        }
    }
}
